/* dystring - dynamically resizing string.
 *
 * This file is copyright 2002 Jim Kent, but license is hereby
 * granted for all use - public, private or commercial. */

#include "common.h"
#include "dystring.h"

struct dyString *newDyString(int initialBufSize)
/* Allocate dynamic string with initial buffer size.  (Pass zero for default) */
{
  struct dyString *ds;
  AllocVar(ds);
  if (initialBufSize == 0)
    initialBufSize = 512;
  ds->string = needMem(initialBufSize + 1);
  ds->bufSize = initialBufSize;
  return ds;
}

void freeDyString(struct dyString **pDs)
/* Free up dynamic string. */
{
  struct dyString *ds;
  if ((ds = *pDs) != NULL) {
    freeMem(ds->string);
    freez(pDs);
  }
}

char *dyStringCannibalize(struct dyString **pDy)
/* Kill dyString, but return the string it is wrapping
 * (formerly dy->string).  This should be free'd at your
 * convenience. */
{
  char *s;
  struct dyString *ds = *pDy;
  assert(ds != NULL);
  s = ds->string;
  freez(pDy);
  return s;
}

void freeDyStringList(struct dyString **pDs)
/* free up a list of dyStrings */
{
  struct dyString *ds, *next;
  for (ds = *pDs; ds != NULL; ds = next) {
    next = ds->next;
    freeDyString(&ds);
  }
  *pDs = NULL;
}

static void dyStringExpandBuf(struct dyString *ds, int newSize)
/* Expand buffer to new size. */
{
  ds->string = needMoreMem(ds->string, ds->stringSize + 1, newSize + 1);
  ds->bufSize = newSize;
}

void dyStringBumpBufSize(struct dyString *ds, int size)
/* Force dyString buffer to be at least given size. */
{
  if (ds->bufSize < size)
    dyStringExpandBuf(ds, size);
}

void dyStringAppendN(struct dyString *ds, char *string, int stringSize)
/* Append string of given size to end of string. */
{
  int oldSize = ds->stringSize;
  int newSize = oldSize + stringSize;
  char *buf;
  if (newSize > ds->bufSize) {
    int newAllocSize = newSize + oldSize;
    int oldSizeTimesOneAndAHalf = oldSize * 1.5;
    if (newAllocSize < oldSizeTimesOneAndAHalf)
      newAllocSize = oldSizeTimesOneAndAHalf;
    dyStringExpandBuf(ds, newAllocSize);
  }
  buf = ds->string;
  memcpy(buf + oldSize, string, stringSize);
  ds->stringSize = newSize;
  buf[newSize] = 0;
}

char dyStringAppendC(struct dyString *ds, char c)
/* Append char to end of string. */
{
  char *s;
  if (ds->stringSize >= ds->bufSize)
    dyStringExpandBuf(ds, ds->bufSize + 256);
  s = ds->string + ds->stringSize++;
  *s++ = c;
  *s = 0;
  return c;
}

void dyStringAppendMultiC(struct dyString *ds, char c, int n)
/* Append N copies of char to end of string. */
{
  int oldSize = ds->stringSize;
  int newSize = oldSize + n;
  int newAllocSize = newSize + oldSize;
  char *buf;
  if (newSize > ds->bufSize)
    dyStringExpandBuf(ds, newAllocSize);
  buf = ds->string;
  memset(buf + oldSize, c, n);
  ds->stringSize = newSize;
  buf[newSize] = 0;
}

void dyStringAppend(struct dyString *ds, char *string)
/* Append zero terminated string to end of dyString. */
{
  dyStringAppendN(ds, string, strlen(string));
}

void dyStringAppendEscapeQuotes(struct dyString *dy, char *string, char quot,
                                char esc)
/* Append escaped-for-quotation version of string to dy. */
{
  char c;
  char *s = string;
  while ((c = *s++) != 0) {
    if (c == quot)
      dyStringAppendC(dy, esc);
    dyStringAppendC(dy, c);
  }
}

void dyStringVaPrintf(struct dyString *ds, char *format, va_list args)
/* VarArgs Printf to end of dyString. */
{
  /* attempt to format the string in the current space.  If there
   * is not enough room, increase the buffer size and try again */
  int avail, sz;
  while (TRUE) {
    va_list argscp;
    va_copy(argscp, args);
    avail = ds->bufSize - ds->stringSize;
    if (avail <= 0) {
      /* Don't pass zero sized buffers to vsnprintf, because who knows
       * if the library function will handle it. */
      dyStringExpandBuf(ds, ds->bufSize + ds->bufSize);
      avail = ds->bufSize - ds->stringSize;
    }
    sz = vsnprintf(ds->string + ds->stringSize, avail, format, argscp);
    va_end(argscp);

    /* note that some version return -1 if too small */
    if ((sz < 0) || (sz >= avail))
      dyStringExpandBuf(ds, ds->bufSize + ds->bufSize);
    else {
      ds->stringSize += sz;
      break;
    }
  }
}

void dyStringPrintf(struct dyString *ds, char *format, ...)
/*  Printf to end of dyString. */
{
  va_list args;
  va_start(args, format);
  dyStringVaPrintf(ds, format, args);
  va_end(args);
}

struct dyString *dyStringCreate(char *format, ...)
/*  Create a dyString with a printf style initial content */
{
  int len = strlen(format) * 3;
  struct dyString *ds = newDyString(len);
  va_list args;
  va_start(args, format);
  dyStringVaPrintf(ds, format, args);
  va_end(args);
  return ds;
}

struct dyString *dyStringSub(char *orig, char *in, char *out)
/* Make up a duplicate of orig with all occurences of in substituted
 * with out. */
{
  int inLen = strlen(in), outLen = strlen(out), origLen = strlen(orig);
  struct dyString *dy = newDyString(origLen + 2 * outLen);
  char *s, *e;

  if (orig == NULL)
    return NULL;
  for (s = orig;;) {
    e = stringIn(in, s);
    if (e == NULL) {
      e = orig + origLen;
      dyStringAppendN(dy, s, e - s);
      break;
    } else {
      dyStringAppendN(dy, s, e - s);
      dyStringAppendN(dy, out, outLen);
      s = e + inLen;
    }
  }
  return dy;
}

void dyStringResize(struct dyString *ds, int newSize)
/* resize a string, if the string expands, blanks are appended */
{
  int oldSize = ds->stringSize;
  if (newSize > oldSize) {
    /* grow */
    if (newSize > ds->bufSize)
      dyStringExpandBuf(ds, newSize + ds->stringSize);
    memset(ds->string + newSize, ' ', newSize);
  }
  ds->string[newSize] = '\0';
  ds->stringSize = newSize;
}

void dyStringQuoteString(struct dyString *dy, char quotChar, char *text)
/* Append quotChar-quoted text (with any internal occurrences of quotChar
 * \-escaped) onto end of dy. */
{
  char c;

  dyStringAppendC(dy, quotChar);
  while ((c = *text++) != 0) {
    if (c == quotChar)
      dyStringAppendC(dy, '\\');
    dyStringAppendC(dy, c);
  }
  dyStringAppendC(dy, quotChar);
}
